Skip to content

[WC-3464] Rich text Tiptapeditor#2270

Open
gjulivan wants to merge 5 commits into
mainfrom
rich-text/tiptapeditor
Open

[WC-3464] Rich text Tiptapeditor#2270
gjulivan wants to merge 5 commits into
mainfrom
rich-text/tiptapeditor

Conversation

@gjulivan

Copy link
Copy Markdown
Collaborator

Pull request type


Description

@gjulivan gjulivan requested a review from a team as a code owner June 17, 2026 11:49
@gjulivan gjulivan force-pushed the rich-text/tiptapeditor branch 2 times, most recently from 584fe21 to 96df6fb Compare July 1, 2026 08:34
@github-actions

This comment has been minimized.

@gjulivan gjulivan force-pushed the rich-text/tiptapeditor branch from 96df6fb to 57a6695 Compare July 1, 2026 08:53
@github-actions

This comment has been minimized.

@gjulivan gjulivan force-pushed the rich-text/tiptapeditor branch from 57a6695 to 3ca1f8c Compare July 2, 2026 21:56
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

AI Code Review

🔶 Changes requested — one or more medium-severity items must be addressed


What was reviewed

File Change
src/RichText.tsx Root widget component — attribute status check
src/RichText.xml Widget manifest — property keys, new properties
src/components/Editor.tsx TipTap editor setup, extensions, event handlers
src/components/EditorWrapper.tsx Wrapper — value sync, status bar logic
src/components/EditorContext.tsx Context + code-view reducer
src/components/StatusBar.tsx Word/character count display
src/components/HighlightedCodeEditor.tsx Inline-style code editor (flagged)
src/components/toolbars/Toolbar.tsx Toolbar orchestration
src/components/toolbars/ToolbarConfig.ts Button/group definitions
src/components/toolbars/components/ToolbarButton.tsx Button renderer + keyboard nav
src/components/toolbars/components/LinkDialog.tsx Link insert/edit dialog
src/components/toolbars/components/ImageDialog.tsx Image insert dialog
src/components/toolbars/components/VideoDialog.tsx Video insert dialog
src/components/toolbars/components/ConfirmDialog.tsx Save/cancel confirmation dialog
src/components/toolbars/hooks/useDropdown.ts Floating UI positioning hook
src/utils/embedCodeParser.ts Embed code security parser
src/extensions/GenericEmbed.ts Generic iframe extension
src/RichText.editorConfig.ts Studio Pro design-time config
src/RichText.editorPreview.tsx Studio Pro preview component
src/__tests__/RichText.spec.tsx Unit tests
src/__tests__/fonts.spec.ts New font helper tests
src/__tests__/helpers.spec.ts New indent-normalisation tests
src/__tests__/customList.spec.ts New custom-list tests
e2e/RichText.spec.js E2E tests
CHANGELOG.md Unreleased section

Skipped (out of scope): dist/, openspec/, src/assets/.gitkeep


Findings

🔶 Medium — Stale unit tests import deleted Quill utilities

File: src/__tests__/fonts.spec.ts line 1
src/__tests__/customList.spec.ts line 1

Problem: Both new test files import from ../utils/formats/fonts and ../utils/formats/customList / ../utils/formats/block — paths that correspond to files deleted in this PR (utils/formats/button.ts, utils/formats/block.ts were removed). If utils/formats/fonts and utils/formats/customList no longer exist (they are not listed in the added files), these tests will fail at import time and the test suite will be broken.

Fix: Verify that src/utils/formats/fonts.ts and src/utils/formats/customList.ts still exist in the new codebase. If they are being replaced by TipTap extensions, delete the orphaned test files and add equivalent coverage against the new extension classes (e.g., FontFamilyClass.ts, Indent.ts).


🔶 Medium — E2E tests still use removed Quill selectors

File: e2e/RichText.spec.js lines 37, 51, 131–139, 144, 156–157, 163

Problem: Several tests click elements using Quill-specific selectors (.ql-toolbar button.ql-image, .ql-toolbar button.ql-view-code, .ql-editor, button.ql-video) and assert against Quill-specific class names (class="ql-color-", class="ql-bg-", data-style-format="class", class="ql-indent-") and modal selectors (widget-rich-text-modal-body). These will all fail against the new TipTap + custom toolbar implementation.

Fix: Update all Quill-specific selectors and class assertions to match the new TipTap DOM. For example, .ql-toolbar button.ql-image should become the TipTap dialog trigger selector, and the class-mode assertions need to match the new class names emitted by FontFamilyClass, Indent, etc. Also replace the two page.waitForLoadState("networkidle") calls (lines 123, 132) with await waitForMendixApp(page) or a web-first assertion per E2E guidelines.


🔶 Medium — useEffect in ImageDialog depends on dialogRef.current (stale closure)

File: src/components/toolbars/components/ImageDialog.tsx lines 146–157

Problem: The dependency array contains [dialogRef.current], but ref.current is a mutable value that React does not track — changes to it do not trigger useEffect re-runs. The eslint-disable comment on line 156 suppresses the correct warning. The listener is also attached with an anonymous closure that closes over the component's current handleImageSelected, which means if the handler ever changes the stale version remains attached.

Fix: Remove the ref.current from the deps array and use the ref object itself, or restructure to attach the listener once on mount:

useEffect(() => {
    const imgRef = dialogRef.current;
    if (!imgRef) return;
    imgRef.addEventListener("imageSelected", handleImageSelected as EventListener);
    return () => {
        imgRef.removeEventListener("imageSelected", handleImageSelected as EventListener);
    };
}, []); // mount/unmount only — dialogRef.current is stable after mount

🔶 Medium — Inline styles on HighlightedCodeEditor violate frontend guidelines

File: src/components/HighlightedCodeEditor.tsx lines 47–57

Problem: The component passes a style object with hard-coded values (fontFamily, fontSize, border, borderRadius, background, etc.) directly to the editor. Per the frontend guidelines, inline styles for static design should not be used; SCSS classes should be used instead.

Fix: Extract these values into src/ui/RichText.scss (or a new CodeEditor.scss) using the .widget-rich-text namespace, and remove the style prop from the component.


⚠️ Low — ToolbarButton has no aria-label on icon-only buttons

File: src/components/toolbars/components/ToolbarButton.tsx lines 104–113

Note: Icon-only buttons rely solely on the title attribute for accessible labelling. The title attribute is not reliably exposed to screen readers (it is a tooltip, not an accessible name). Add aria-label={config.title} to each <button> so screen readers announce the button's purpose correctly.


⚠️ Low — ConfirmDialog uses a global document listener without FloatingFocusManager

File: src/components/toolbars/components/ConfirmDialog.tsx lines 15–24

Note: The dialog does not trap focus — a user navigating with Tab can leave the dialog while it is open. Per the accessibility guidelines, dialogs/modals should trap focus. Consider using a <dialog> element (which provides native focus trapping) or wrapping with Floating UI's FloatingFocusManager.


⚠️ Low — URL input in LinkDialog lacks javascript: / data: URI guard

File: src/components/toolbars/components/LinkDialog.tsx lines 36–76

Note: The handleSubmit only checks url.trim() is non-empty before calling setLink. A user can type javascript:alert(1) and insert it as a link href. The linkValidation prop exists on the widget but is not plumbed through to LinkDialog. Add a client-side check:

const UNSAFE_PROTOCOLS = /^(javascript|data|vbscript):/i;
if (UNSAFE_PROTOCOLS.test(urlValue)) {
    // reject
    return;
}

⚠️ Low — EditorWrapper does not guard stringAttribute.setValue against read-only state

File: src/components/EditorWrapper.tsx lines 48–60

Note: stringAttribute.setValue(html) is called without first verifying !stringAttribute.readOnly. Although toolbarLocation is set to "hide" in read-only mode which prevents toolbar edits, direct programmatic updates (e.g. from extension commands) could still call onUpdatehandleUpdatesetValue. Add a guard: if (!stringAttribute.readOnly) { stringAttribute.setValue(html); }.


Positives

  • The embedCodeParser.ts has exemplary security: it uses DOMParser rather than regex/string manipulation, validates the src URL against a domain allowlist, and explicitly blocks javascript: and data: URIs.
  • GenericEmbed.ts renders iframes with sandbox, loading="lazy", and referrerpolicy security attributes — the right defaults for third-party embeds.
  • EditorContext uses a useReducer rather than scattered useState calls to manage the code-view state machine — clean and predictable.
  • New unit tests (helpers.spec.ts) thoroughly cover the indent normalisation utility including RTL, rounding, and edge cases.
  • useDropdown.ts correctly cleans up the global mousedown listener in the useEffect return function.
  • The CHANGELOG entry is present and user-facing (describes behaviour, not implementation details).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant